package cn.cmeizu.bean.impl;

import cn.cmeizu.annotation.Bean;
import cn.cmeizu.annotation.Di;
import cn.cmeizu.bean.ApplicationContext;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author cmeizu
 * @date 2023/2/11 17:41
 */
public class AnnotationContextApplicationContext implements ApplicationContext {
  //创建一个map放bean实例对象
  private Map<Class<?>, Object> beanFactory = new HashMap<>();

  /**
   * 根路径
   */
  private static String rootPath;

  //返回创建之后的对象
  @Override
  public Object getBean(Class<?> clazz) {
    return beanFactory.get(clazz);
  }

  //设置包扫描的规则
  //当前包及其子包，哪个类有@Bean注解，把这个类通过反射实例化
  public AnnotationContextApplicationContext(String basePackage) {
    try {
      //cn.cmeizu
      //1. 把.换成\
      String packagePath = basePackage.replaceAll("\\.", "/");
      //2. 获取包的绝对路径
      Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
      while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        String filePath = URLDecoder.decode(url.getFile(), "utf-8");
        //获取包前面路径部分，字符串截取
        rootPath = filePath.substring(0, filePath.length() - packagePath.length());
        //包扫描的过程
        loadBean(new File(filePath));
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    //属性注入
    loadDi();
  }

  private void loadDi() {
    //实例化的对象在beanFactory的map集合里面
    //1. 遍历beanFactory的map集合
    Set<Map.Entry<Class<?>, Object>> entries = beanFactory.entrySet();
    for (Map.Entry<Class<?>, Object> entry : entries) {
      //2. 获取map集合每个对象(value),每个对象属性获取到
      Object obj = entry.getValue();
      //获取对象Class
      Class<?> clazz = obj.getClass();
      Field[] declaredFields = clazz.getDeclaredFields();
      for (Field field : declaredFields) {
        //3. 遍历得到每个对象属性数组，得到每个属性
        Di annotation = field.getAnnotation(Di.class);
        //4. 判断属性上面是否有@Di注解
        if (annotation != null) {
          //如果私有属性，设置可以设置值
          field.setAccessible(true);
          //5. 如果有@Di注解，把对象进行设置(注入)
          try {
            field.set(obj, beanFactory.get(field.getType()));
          } catch (IllegalAccessException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }

  private void loadBean(File file) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    //1. 判断当前是否是文件夹
    if (file.isDirectory()) {
      //2. 获取文件夹里的所有内容
      File[] childrenFiles = file.listFiles();
      //3. 判断文件夹里面为空，直接返回
      if (childrenFiles == null || childrenFiles.length == 0) {
        return;
      }
      for (File child : childrenFiles) {
        //4. 如果文件夹不为空，遍历文件夹所有内容
        if (child.isDirectory()) {
          //4.1 遍历得到每个file对象，继续判断，如果还是文件，递归
          loadBean(child);
        }
        //4.2 遍历得到file对象不是文件夹，是文件
        //4.3 得到包路径+类名称部分,字符串的截取过程
        String pathWithClass = child.getAbsolutePath().substring(rootPath.length());
        //4.4 判断当前文件的类型.class
        if (pathWithClass.contains(".class")) {
          //4.5 如果是.class，把路么/替换成. 把.class去掉 cn.cmeizu.service.UserServiceImpl新建对象
          String allName = pathWithClass.replaceAll("/", "\\.").replace(".class", "");
          //4.6 判断类上面是否有注解@Bean,如果有进行实例化过程
          //4.6.1 获取类的Class对象
          Class<?> clazz = Class.forName(allName);
          //4.6.2 判断不是接口
          if (!clazz.isInterface()) {
            //4.6.3 判断上面是否有注解
            Bean annotation = clazz.getAnnotation(Bean.class);
            if (annotation != null) {
              //4.6.4 实例化
              Object instance = clazz.getConstructor().newInstance();
              //4.7 对象实例化后放到map,beanFactory
              //4.7.1 判断当前类如果有接口，让接口的class作为map的key
              if (clazz.getInterfaces().length > 0) {
                beanFactory.put(clazz.getInterfaces()[0], instance);
              } else {
                beanFactory.put(clazz, instance);
              }
            }
          }
        }
      }
    }
  }
}
